home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
CICA 1993 April
/
CICA MS Windows - April 1993.iso
/
unzipped
/
programr
/
wpjv1n3
/
wpjv1n3.txt
< prev
Wrap
Text File
|
1993-03-03
|
126KB
|
3,718 lines
WW WW WW PPPPPPPP JJ
WW WW WW PP PP JJ
WW WWWW WW PP PP JJ
WW WW WW WW PPPPPPPP JJ
WW WW WW WW PP JJ JJ
WWWW WWWW PP JJ JJ
WW WW PP JJJJJ
----------------------------------------------------------------
The Windows Programmer's Journal Volume 01
Copyright 1993 by Peter J. Davis Number 03
and Mike Wallace Mar 93
----------------------------------------------------------------
A monthly forum for novice-advanced programmers to share ideas and concepts
about programming in the Windows (tm) environment. Each issue is uploaded
to the info systems listed below on the first of the month, but made
available at the convenience of the sysops, so allow for a couple of days.
You can get in touch with the editors via Internet or Bitnet at:
HJ647C at GWUVM.BITNET or HJ647C at GWUVM.GWU.EDU
CompuServe: 71141,2071
GEnie: P.DAVIS5
or you can send paper mail to:
Windows Programmer's Journal
9436 Mirror Pond Dr.
Fairfax, Va. 22032
We can also be reached by phone at: (703) 503-3165.
The WPJ BBS can be reached at: (703) 503-3021.
The WPJ BBS is currently 2400 Baud (8N1). We'll be going to 14,400 in the
near future, we hope.
LEGAL STUFF
- Microsoft, MS-DOS, Microsoft Windows, Windows NT, Windows for Workgroups,
Windows for Pen Computing, Win32, and Win32S are registered trademarks of
Microsoft Corporation.
- Turbo Pascal for Windows, Turbo C++ for Windows, and Borland C++ for
Windows are registered trademarks of Borland International.
- Other trademarks mentioned herein are the property of their respective
owners.
- WordPerfect is a registered trademark of WordPerfect Corporation.
- WPJ is available from the WINSDK, WINADV and MSWIN32 forums on
CompuServe, and the IBMPC, WINDOWS and BORLAND forums on GEnie. It is also
available on America Online in the Programming library. On Internet, it's
available on WSMR-SIMTEL20.ARMY.MIL and FTP.CICA.INDIANA.EDU. We upload it
by the 1st of each month and it is usually available by the 3rd or 4th,
depending on when the sysops receive it.
- The Windows Programmer's Journal takes no responsibility for the content
of the text within this document. All text is the property and
responsibility of the individual authors. The Windows Programmer's Journal
is solely a vehicle for allowing articles to be collected and distributed
in a common and easy to share form.
- No part of the Windows Programmer's Journal may be re-published or
duplicated in part or whole, except in the complete and unmodified form of
the Windows Programmer's Journal, without the express written permission of
each individual author. The Windows Programmer's Journal may not be sold
for profit without the express written permission of the Publishers, Peter
Davis and Michael Wallace, and only then after they have obtained
permission from the individual authors.
Table of Contents
Subject Page Author(s)
-----------------------------------------------------------------
WPJ.INI ....................................... 4 Pete Davis
Letters ....................................... 7 Readers
Beginner's Column ............................. 10 Dave Campbell
Install Program Part III ...................... 20 Pete Davis
Home Cooking - C++ From Scratch ............... 22 Andrew Bradnan
Creating and Using Owner Draw Buttons ......... 26 Todd Snoddy
Hacker's Gash ................................. 30 Mike and Pete
Special News .................................. 32 Mike Wallace
Windows 3.1: Using Version Stamping Library ... 33 Alex Fedorov
Book Review ................................... 36 Pete Davis
Book Review ................................... 38 Mike Wallace
Printing in Windows ........................... 40 Pete Davis
Advanced C++ and Windows ...................... 45 Andrew Bradnan
Trials and Tribulations Part 1 ................ 54 Jim Youngman
Getting in Touch with Us ..................... 57 Pete & Mike
Last Page .................................... 58 Mike Wallace
Windows Programmer's Journal Staff:
Publishers ......................... Pete Davis and Mike Wallace
Editor-in-Chief .................... Pete Davis
Managing Editor .................... Mike Wallace
Contributing Editor ................ David Campbell
Contributing Editor ................ Andrew Bradnan
Contributing Writer ................ Dave Campbell
Contributing Writer ................ Alex Federov
Contributing Writer ................ Andrew Bradnan
Contributing Writer ................ Jim Youngman
Contributing Writer ................ Todd Snoddy
WPJ.INI
by Pete Davis
Issue #3, wow... So far things have been going really well. The third
issue is almost as big as the first and second put together! We've even got
some extra articles for next month. We're getting a good response from the
readers and the contributions are coming in. It seems like every day we get
a letter from someone in another country. We've been getting mail from
people in Romania, Czechoslovakia, Russia, Hong Kong, Australia, England,
Germany, etc... In fact, the amount of mail we're getting is getting pretty
high. We try to answer it all, but we don't always get a chance to. Also,
some of the replies don't make it back. I've noticed this more often with
certain Internet sights in the U.K. I don't know why that is, but the ones
that have something like ac.uk in them don't seem to make it back. Sorry
about that. The list goes on and on. Keep the mail coming, we love to get
it.
I mentioned in the last issue about readers suggesting different
formats for the text. So far, the biggest response has been in favor of the
WINHELP format and the plain text format. That way you can read it on-
screen and, if you want, print it out. Speaking of printing it out, we've
had some negative responses about the printing format. I have to take
responsibility for that. I had the page length a bit too long and a lot of
you were getting pages with three or four lines of text. I'll try not to
let that happen again.
As far as the Help format, because we can do bitmaps now, it would be
nice if we could get someone who is artistically inclined (Not us, as
you'll see in this first issue) to give us a hand with some of the
graphics. You don't have to be a Renoir, just better than us. If you can
draw stick figures, you probably qualify.
So, this issue we'll be starting with the WINHELP format. We hope you
like it. We're pretty pleased with the idea. Right now we're using minimal
tools for getting it into the help format, but we're looking into getting
some better ones later on.
We'd like to thank the guys at America Online for giving us some time
each month to get on and be able to stay in touch with our readers there.
Mike will have more to say about all this in the Last Page. [See the
"Special News" column - MW]
This month I'm going to be doing my article on printing that I should
have done last month. Sorry for the delay, but we've just been real busy.
I'm also going to do the third article in the series on the install
program. This article is basically going to give some insight into the data
structure that we're going to use for storing data about each of the files
for the install.
This month David Campbell will be taking over the Beginners Column in
C and we have Andrew Bradnan taking over the Beginners Column in C++. (We
could still use someone to do the Beginner's Column in Pascal for Windows).
- 4 -
This brings me to another point. Beginner's Column might be a bit of a
misnomer. Yes, they'll all be basic, but like anything else, as time goes
on, the articles will progress. They will all be, eventually, an
intermediate programmer's column. There's only so much that you can say
about the basics and eventually you have to move on.
We're starting a new feature this month called Hacker's Gash. Hacker's
Gash is going to be a bunch of little tips and tricks that you can use in
Windows. This is where the readers can really get in on the fun. Read
through it, get an idea of what kinds of things we're doing and send in a
list of your own tricks.
Ah, good news, the BBS is finally up. Haven't replaced the hard drive,
but I've done some serious work on it and did some pretty strenuous testing
on it and it seems to be ok for now. I will need to replace it eventually,
but we don't have the money right now for that. Besides being the first
place to get the Windows Programmer's Journal, the WPJ BBS has the SIMTEL20
CD-ROM which has 600+ megs of public domain and shareware software. We will
try to add a second CD-ROM in the near future and add some new disks. We'll
probably try to add the CICA disk next, which has a very large library of
Windows public domain and shareware software. The number's around here
somewhere, but to make it easier, and as long as you're reading here, it's
(703) 503-3021. (That's in the U.S. for you guys overseas.)
Mike and I have debated doing this, but we're really getting to the
point where we don't have much choice. Seeing as we're going into the
Windows Help format, which will allow us to support graphics, we've decided
that we're going to start allowing some advertisers in the magazine. We're
going to keep the ads to a minimum, but, even though the magazine is free,
there are some costs involved in putting it together and we really need
some reimbursement for it. Right now, our Compuserve and Genie bills alone
are costing us a fair amount. Running the BBS will cost us $25/month in
phone bills alone, not to mention possible hardware maintenance. We're
hoping to get a few advertisers to help offset some of those costs. The ads
will be, most likely, from shareware authors. If you are a Windows
shareware author and you're interested in advertising, get in touch with us
and we'll discuss the costs, size, design, etc.
One last thing before I wrap up. We've gotten a lot of comments about
the format of the magazine. Most of these were in reference to the format
we're going to distribute it in. If you have preferences about the layout
or other stylistic concerns, we'd like to hear them.
That's really about it for this month, I guess. We just wanted to
thank all of you for reading and to keep the comments and suggestions
coming. We'd also like to thank those of you who have submitted articles
and we ask that you please keep the articles coming in. It would be nice if
we got a few more submissions each month so we could get the magazine up to
the size that we'd really like. I think 50 pages would be a good size,
although, with something like this, I suppose, the bigger the better, so
more than 50 pages would be fantastic. Well, I've said enough. Enjoy the
magazine.
- 5 -
_Pete Davis
P.S. I just read Mike's Last Page (He does it last, so I didn't see it when
I was writing the WPJ.INI) You might want to read that and then come back
to this, but I wanted to give my own thoughts on what Mike said regarding
the WPJ as a source of information. We are not here to be a sole source of
information, nor will we always be correct. We do our best to make sure
that the information we give out is correct. The only people who check the
submissions are Mike and myself. Neither one of us has a PhD in Windows
Programming. We both make mistakes. The WPJ is going to have errors in it.
We're going to give out completely wrong information at times. When that
happens, we will try to correct ourselves by the next issue (usually in
response to someone saying "You guys screwed up!").
What I'm saying is that there are a lot of sources out there. You need
to check out as many as you can. Cross-reference the information between
different books and magazines and when you see an inconsistency, that's
probably a fault.
Mike and I have several reasons for doing the WPJ. First of all, we
felt beginners weren't being addressed well enough. Also, we wanted to
cover as many Windows programming languages and environments as possible.
(We're getting there, but slowly.) Most important, I think, we wanted to
address Windows programming and Windows programming only.
Windows is an enormous programming environment and there's tons of
information to absorb. No one publication can even approach covering it
all. I like to think that one of our advantages is that, unlike a lot of
other programming magazines, we don't center on a topic each month. Other
magazines might have, say, an issue on Custom Controls or an issue on
Multi-media. Well, that's great, but what about those of us who don't work
with Custom Controls or Multi-media? There are two issues that we don't
care for. We try to keep a variety of topics each month so we can appeal to
as many people as possible with each issue, but I digress.
To wrap up, I just want to say, (this feels like deja vu, I'll check
later, but I think I said this in the last issue also) don't count on us
being right every time. If something we wrote about doesn't work, try it
again. If it still doesn't work, we probably screwed up. Let us know, and
we'll try to correct it by the next issue. I know, I'm getting repetitive.
I know, I'm getting repetitive. Until the next issue, peace.
- 6 -
Letters
Date: 10-Feb-93 07:04 EST
From: Chris Newham [100027,16]
Subj: Windows Programmer's Journal
Hi,
I would just like to say that I have enjoyed the two issues of WPJ so far.
You can add at least 1 to your readership numbers as my friend and I
download it once between us.
The sections on DLLs & install progs are of particular interest to us as we
have just finished work on a DLL and are now working on the install prog. A
point that I think you might mention and give some consideration to for the
install prog is this:
If you are installing DLLs then you need to check if the file exists first
(easy); if it exists you then need to check its version control information
to determine if the copy you wish to install is newer (also quite easy);
the difficult part comes when you have determined that you need to replace
the existing DLL but the DLL is already in use by Windows.
Windows will not let you delete or replace the file. Microsoft's install
program works around this but we haven't yet worked out how; we think that
it copies the new DLL to a temp dir or hides it somewhere then replaces it
when Windows next starts up.
It's an interesting little problem and has at present got us stumped. If
you know how to solve it let's see it published. If we find a solution we
will let you know. [We're working on this problem, too. - MW]
I am very impressed by the quality of the journal and would like to see it
remain in either text or Windows write format as my time on the PC is
limited and so what I do is take a hard copy of the journal to work to read
at lunch time so Winhelp format would not suit me.
Keep up the good work Best wishes Chris.
- 7 -
Date: 12-Feb-93 17:29 EST
From: Tammy Steele
Subj: WPJ
Hi Mike,
I just downloaded my mail today. I generally only read it once a
month. So, sorry for the delay in my comments about WPJ. I had a chance to
skim the first issue and have just spent some time reading through the
second issue. Of course I cannot make official "Microsoft" comments, but I
can give you my opinion.
Generally speaking, I think the WPJ will be another good place for us
to point people to for information. One problem will be getting the
Microsoft support engineers familiar with the content of the journal.
Another problem is the accuracy of the articles. In order for Microsoft to
point people to the articles, we need review the accuracy of them. We are
pretty swamped now with too much to do, not enough people. I am sending
mail to my group about the journal. So we'll see what happens.
Specifically, I thought it was good that you discussed the feedback
you received from the first journal. Especially the Linked List sample.
If the sample were used for a large number of nodes, as I'm sure someone
probably mentioned, it would not be a "cooperative" windows app because
each globalalloc eats a selector and the selectors (8K of them) are shared
between *all* windows apps and the Windows system itself.
Also, it would be useful for samples to be updated to the current
software although there is still value to having the code and comments (ie,
the DLL article.)
People who submit articles should check the MSKB and if they have
areas that they are uncertain about, should post questions in the forum
before they submit articles. For example, the DLL article talks about WEPS
and says something like "the documentation says the WEP is called
once...but I haven't seen it be called in Codeview in Windows 3.0." There
is an article that discusses the reason why you can't see it be called in
Codeview under Windows 3.0 and/or this question could have been easily
answered in the DLL section on WINSDK. [This question is answered farther
down in this column. - MW]
I like the coding style and the sample makefiles (the makefiles are
easy to read.)
I'm sure you've had lots of comments, as you mentioned, about how to
format this. I personally think you should offer it in helpfile format
*and* text format so that people have the option. [editors note: This seems
to be the most common opinion.]
Overall, I see this journal as a really great place for people to
compile information and share it.
Thanks for all your work, Tammy
- 8 -
Editor's Note: We got a letter from Craig Derouen (CompuServe ID: 75136,
1261) of Seattle. I'll reprint his letter here and let him explain his
BBS:
I've looked at your 2 first issues of WPJ and am impressed. I run an
extensive programmer's BBS out here in Seattle, primarily Windows code but
also C, C++ and 8086 code for DOS. I carry both your issues. I am also in
Fidonet and have all the listings for WPJ available for Frequest. Also I
am a support BBS for WinTech,Windows/DOS,CUJ and PC Techniques Magazine. I
have a LOT of code on line. Anyways here's the details:
Cornerstone BBS (2400,9600 baud)
(206) 362-4283
Fidonet 1:343/22
All magazine listings are available for first time callers; free downloads
and some file requests are also available.
---------------
Editor's Note: In our last issue, we had an article by Rod Haxton on DLLs.
In the article, he mentioned that he had never seen WEP get called under
CodeView in Windows 3.0. So, I posed the question to the WINSDK forum on
CompuServe and got a response from Brian Scott of Microsoft. Thanks,
Brian.
Mike,
If you implicitly link to a DLL in Windows 3.0, the WEP is called
after the application has unloaded. Since Codeview stops debugging after
the application terminates, it does not see the WEP get called. If you
need to debug the WEP, you can load it using LoadLibrary() and free it
using FreeLibrary(), instead of linking to the import library. In this
case, you will see the WEP get called when you call FreeLibrary(). If you
need to link to the DLL using an import library, then you can debug the WEP
using something like WDEB386, putting OutputDebugString() statements in
your WEP, or moving the code in the WEP into a cleanup function that you
call just before the application terminates.
Hope this helps,
Brian Scott - Microsoft
- 9 -
Editor's Note: Pete and I started a beginner's column with the first issue,
and Dave Campbell offered last month to take it over. Dave is a confirmed
Windows hacker and so will be writing this column starting with this issue.
We hope you like it. Questions should be directed to Dave. Means of
reaching him are given at the end of his column.
Beginner's Column
By Dave Campbell
My name is Dave Campbell and I write Windows software under the
business name WynApse. I am going to be writing the beginner's column until
I get buried in work (wouldn't that be too bad?) or I get usurped by
someone who wants to do this more than I do.
Up until recently, I have been a Borland programmer, so the tools I am
most familiar with are the Borland C++ suite, 3.0 and 3.1, in particular.
Pete and Mike's previous two columns have been Microsoft, and I am leaning
that way myself right now, so I will continue that. My intention is to
provide code and ideas that aren't tied to Borland or Microsoft, but could
be used with either compiler. A major difference between the two is make
files, so I will stay away from those as much as possible.
My intention is to write code that is useable on 3.0/3.1 Windows. If
enough questions come in for 3.1-specific applications, we will cover them.
This leads directly into the request for requests. I would like to present
code that is useful to all the readers, and the best way would be to be
responsive to readers questions. Please send questions! I will list various
ways of reaching me at the end of each article.
Now on to the fun stuff...Mike and Pete have been working on a Hello
World program for a few issues, and I am going to add some configuration to
that code and demonstrate some basic uses for radio buttons and INI files.
Radio Buttons
A radio button is one of several control types available to Windows
programmers inside dialog boxes. They work similar to the buttons on your
car radio...only one may be pressed at a time, at least in this
application. There are other controls available in the basic set, and we
will look into them later. For now, let's look a little deeper into the
declaration of a radio button.
Radio button declarations are made in the .RC file, for example:
CONTROL "Hello", IDM_HELLO, "BUTTON",
BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 20, 15, 42,
12
We will use one more button in our dialog box, and that is a
pushbutton. The ever-present OK button to be precise. The control is a
graphical
- 10 -
representation of a 3D button with text written on it. By making a
pushbutton an "OK button", we are really painting the word OK on the face
of a 3D pushbutton. The button definition we are going to use is:
CONTROL "OK", IDOK, "BUTTON",
BS_DEFPUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 29, 57, 24, 14
The explanation of the fields in these two declarations is covered
later in this article.
INI files
INI files are used by Windows to control the environment under which
Windows executes. Windows programs use INI files to manage the individual
environmental changes available to users to customize the system to his
needs. There are two ways to read a variable from an INI file, but only one
way to write information into one:
Reading:
ReadPrivateProfileString
ReadPrivateProfileInt
Writing:
WritePrivateProfileString
This does seem clunky, but it's what we've got to work with. When we
get to that point, I'll demonstrate that it's not that big of a problem.
Windows handles the INI files very nicely, in that you will always get
something for your efforts. In the Read calls, a default value is given, in
case the variable does not exist in the INI file (or the INI file doesn't
exist, for that matter). In the Write call, if the variable doesn't exist
(or even if the INI file doesn't exist) prior to the call, it will when the
call returns. This means that you needn't be concerned about the pre-
existence of an INI file before trying to use one.
Let's consider an INI file for our application, HELLO.INI:
[Hello]
Setup=0
This is the simplest form of an INI file: there is a single
'Application Name', [Hello], and a single 'Keyname', Setup, that is
associated with it. The value for Setup is 0. During execution of our
program, if we had a need for picking up the Setup value, we would simply
use the line:
nDflt = ReadPrivateProfileInt("Hello", "Setup", 0, "hello.ini");
- 11 -
where nDflt is defined:
int nDflt;
This reads from hello.ini, looking for the 'Setup' keyword under the
'Hello' application name, and assigning the value equivalence listed there,
or 0 for default, to the variable nDflt.
If, however, the INI file was defined as:
[Hello]
Setup=Hello World
you now cannot read the file with ReadPrivateProfileInt. Now the line
to read the value is:
char szDflt[25];
ReadPrivateProfileString("Hello", "Setup", szDflt, "Goodbye World",
13, "hello.ini");
This has some similarities, and some differences from the one above.
The "Hello", "Setup", and "hello.ini" are self-explanatory, but the string
read also adds other parameters. The string name to store the result into
is listed as the third parameter, in this case szDflt. The fifth parameter
is an integer variable declaring the maximum number of characters accepted
from the INI file, and the fourth parameter, in this case "Goodbye World",
is the default value. If this line were to be executed and the INI file not
even exist, szDflt would contain the string "Goodbye World". This is also
the case if the file exists but does not contain either [Hello] or Setup,
or both.
To change an INI file, the WritePrivateProfileString function must be
called:
char szDflt[25];
int nDflt;
wsprintf(szDflt, "%d", nDflt);
WritePrivateProfileString("Hello", "Setup", (LPSTR) szDflt, "hello.ini");
'wsprintf' should be used in place of 'sprintf' because it is already
in Windows, and will not cause the linking to another library. The
downside is the need to cast the string as a LPSTR. wsprintf will build a
string, in this case, containing the ASCII representation of the single
integer nDflt. That string is then passed to the INI file using the syntax
shown. If the Setup variable were a string, the variable to be inserted
would be given in the WritePrivateProfileString call rather than using the
intermediate wsprintf step.
Whew, I'm glad that's over.
- 12 -
Dialog Box
The dialog box we're going to display will have two radio buttons, and an
OK button. Typically, the buttons to complete a dialog box are located
either along the bottom, or if the dialog is very busy, they can be placed
along the right edge. Of course, this has nothing to do with programming
Windows. This is all aesthetics and being kind to users. Look at a thousand
Windows applications, and you'll get used to seeing things a certain way.
Users get used to them being that way, and you will lose some people just
with your user interface, or lack thereof.
The code I propose for the dialog box is:
Hello DIALOG 63, 56, 83, 77
STYLE WS_POPUP | WS_CAPTION
CAPTION "Hello Setup"
FONT 8, "Helv"
BEGIN
CONTROL "Hello", IDM_HELLO, "BUTTON",
BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 20, 15, 42,
12 CONTROL "Goodbye", IDM_GOODBYE, "BUTTON",
BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 20, 28, 42,
12 CONTROL "OK", IDOK, "BUTTON",
BS_DEFPUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 29, 57, 24,
14 END
This code can be produced either by hand with any text editor, or by
graphically placing the objects with Borland's Resource Workshop or some
similar tool. I usually start with the Resource Workshop, and make small
tweeks by hand. Large changes are best done graphically to ensure the
placement, and logical usefulness of the dialog box is alright.
I am going to take a break right here and talk about coding style.
This is something that is as peculiar to a person as their name. Everybody
has their own, and I wouldn't think of trying to force my ideas on someone.
But...(have you ever noticed there's always a big But around somewhere?),
if you don't have your mind made up yet about those long Windows lines like
the 100+ character ones above, please split them onto succeeding lines!
Line-wrap on listings is UGLY!! OK, that's over, I've got it out of my
system. But (another one), since I am writing this, you are going to have
to put up with my style...
I'm done now. That short dissertation was free, now back to the
program. The field explanations are as follows:
Hello DIALOG 63, 56, 83, 77
This line defines the name, "Hello" of the dialog, and the coordinates
of the box relative to the client area of the parent window. The units are
NOT pixels, and beyond that, I don't want to get into it now. Just play
with it for now until you get it where you like it. The first two numbers
are the x,y coordinates, and the second two are the width and height of the
- 13 -
dialog.
STYLE WS_POPUP | WS_CAPTION
The STYLE line defines the manner in which the dialog box is built.
WS_POPUP is pretty standard for dialog boxes. There are real differences
between POPUP windows and OVERLAPPED windows, the main one being the
CAPTION on a POPUP is an option. Because we are going to call this as a
modal dialog box, we are going to give it a caption bar to allow it to be
moved. 'Modal' dialog boxes, as opposed to 'modeless', disable the parent
window, and demand attention until closed. Without the caption bar, the box
cannot be moved.
CAPTION "Hello Setup"
The CAPTION line declares the caption used with the WS_CAPTION style
parameter.
FONT 8, "Helv"
This defines the default font used throughout the dialog box. You
could pick any conceivable font here, but it is best to be kind and only
use those shipped to Windows users, or you are going to get some nasty mail
messages.
BEGIN
BEGIN..END or {..} define the body of the dialog box.
CONTROL "Hello", IDM_HELLO, "BUTTON",
BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 20, 15, 42, 12
CONTROL "Goodbye", IDM_GOODBYE, "BUTTON",
BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 20, 28, 42, 12
The two radio button definitions following the declaration CONTROL
are:
- The text of the control, in our case "Hello" or "Goodbye"
- The ID value to be returned to the parent window, IDM_HELLO or
IDM_GOODBYE are defined in our ".H" file.
- BUTTON declares the control class. Buttons are generally small child
windows.
- BS stands for BUTTON STYLE, and BS_AUTORADIOBUTTON declares the buttons
will only be pressed one at a time, and the button is automatically
checked.
- WS_CHILD declares the dialog box as a child window. This means it resides
within the boundaries of the parent window.
- WS_VISIBLE applies to overlapped and popup windows. The initial condition
- 14 -
is visible.
- WS_TABSTOP specifies that the user may step through the control sequence
with the tab key.
- 20, 28, 42, 12 are the coordinates and size of the control, similar to
that of the dialog box itself.
CONTROL "OK", IDOK, "BUTTON",
BS_DEFPUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 29, 57, 24, 14
The OK button declaration: everything should be self-explanatory
except the BS_DEFPUSHBUTTON style. DEFPUSHBUTTON means that this is the
default PUSHBUTTON. Pushbuttons are: OK, CANCEL, ABORT, RETRY, IGNORE, YES,
NO, and HELP. Only one may be the default, and it has a dark border around
it, so that if the Enter key is pressed, that is the one you get. IDOK is
defined in windows.h, so we don't have to declare it.
The user will get this dialog box on the screen, and select one of the
radio buttons, or toggle them back and forth a few times, then select OK.
The dialog box procedure should read the INI file and preset the proper
radio button to show the user the current value. Upon selecting OK, the
procedure will have to set the selected information into the INI file.
That completes the dialog box procedure discussion. The code follows:
BOOL FAR PASCAL HelloDlgProc (HWND hDlg, WORD message, WORD wParam,
LONG lParam) {
switch (message)
{
case WM_INITDIALOG :
CheckRadioButton(hDlg, IDM_HELLO, IDM_GOODBYE, InitSettings);
return TRUE;
case WM_COMMAND :
switch (wParam)
{
case IDM_HELLO :
WritePrivateProfileString("Hello", "Setup", "1",
"Hello.ini");
InitSettings = wParam;
break;
case IDM_GOODBYE :
WritePrivateProfileString("Hello", "Setup", "0",
"Hello.ini");
InitSettings = wParam;
break;
case IDOK :
EndDialog(hDlg, wParam);
- 15 -
return TRUE;
}
break;
}
return FALSE;
} /* HelloDlgProc */
Notice the WM_INITDIALOG call:
CheckRadioButton(hDlg, IDM_HELLO, IDM_GOODBYE, InitSettings);
An assumption is being made here that the variable InitSettings has
been read into our program somewhere, and is set to (in our case) either
IDM_HELLO or IDM_GOODBYE. CheckRadioButton uses the dialog box handle hDlg
to identify a numerical sequence of buttons from IDM_HELLO to IDM_GOODBYE,
and ensures that only InitSettings is set.
More advanced Windows programmers will probably want to skip the
case IDM_HELLO
.
case IDM_GOODBYE
statements altogether, and wait until OK is pressed to check the state of
the buttons. That is fine, and more streamlined, but for now, let's not
leave anyone behind. I want all of us to learn this stuff.
Let's change one more function, or else the whole idea of the dialog
box selecting radio buttons is wasted. Let's read the INI file in WndProc,
and change the text displayed based upon the Select value.
This is going to take two steps, just like the two parts of the
previous sentence:
case WM_CREATE :
InitSettings = GetPrivateProfileInt("Hello", "Setup", 1, "Hello.ini");
InitSettings = InitSettings ? IDM_HELLO : IDM_GOODBYE;
hdc = GetDC(hWnd);
InvalidateRect(hWnd, NULL, TRUE);
ReleaseDC(hWnd, hdc);
and:
/*-------------------------------------------------------------------------
fall through to WM_PAINT...
--------------------------------------------------------------------------
*/ case WM_PAINT :
hdc = BeginPaint(hWnd,&ps); /* returns pointer to hdc
- 16 -
*/ GetClientRect(hWnd, &rect);
/*
-1 tells the DrawText function to calculate length of string based on
NULL-termination
*/
DrawText(hdc, (InitSettings == IDM_HELLO) ? "Hello Windows!" :
"Goodbye Windows!", -1, &rect, DT_SINGLELINE | DT_CENTER |
DT_VCENTER); EndPaint(hWnd,&ps);
return 0;
The WM_CREATE case reads the INI file to find out which we want
printed and sets up the 'InitSettings' variable used in the dialog box.
Then we get a "handle to a device context" or hdc for our window.
A device context is the area into which we "paint" text in a window.
In a dialog box, we can use wsprint, but in a window, we have to
"DrawText", and we draw it into a device context. A device context could be
a window or a printer page, Windows doesn't care. At this point, we are
getting the device context for our main window's client area. We then
report to Windows that the entire area is invalid, which will set us up for
a repaint.
In handling the WM_PAINT case, we again need an hdc, and this time do
it with a call to BeginPaint, passing a pointer to a structure variable of
type PAINTSTRUCT. BeginPaint has Windows fill in the ps for us, and is then
available for our use. We aren't going to use it, however.
We call GetClientRect to get the dimensions of the client area into
the rect structure.
DrawText uses the hdc, the rect structure, and the InitSettings value
to decide what to paint and where. Ultimately, either "Hello Windows!", or
"Goodbye Windows!" is printed on a single line, centered horizontally and
vertically: DT_SINGLELINE | DT_CENTER | DT_VCENTER. Notice the note above
the DrawText line. Instead of telling Windows how many characters we are
painting, let Windows do it for us!
EndPaint closes the ps structure, and finishes our WM_PAINT case.
The Setup dialog
I almost forgot, we do need to get the dialog box onto the screen. I
have added three lines to the hello.c file:
hMenu = GetSystemMenu(hWndMain, FALSE);
AppendMenu(hMenu, MF_SEPARATOR, 0, NULL);
AppendMenu(hMenu, MF_STRING, IDM_SETUP, "Setup...");
These get a handle to the system menu of the window, and insert a menu
- 17 -
separator followed by the word "Setup...". When "Setup..." is chosen, the
value IDM_SETUP is sent to our windows message loop:
case WM_SYSCOMMAND :
switch (wParam)
{
case IDM_SETUP :
lpfnHelloDlgProc = MakeProcInstance(HelloDlgProc, hInst);
DialogBox(hInst, "Hello", hWnd, lpfnHelloDlgProc);
FreeProcInstance(lpfnHelloDlgProc);
return 0;
}
break;
This is handled as WM_SYSCOMMAND, because the system menu is the one
used.
We must get a long pointer to a function, lpfnHelloDlgProc, to use in
the DialogBox function call. The parameter "Hello" in the call is the title
of the dialog box we built. Because of the 'DialogBox' call, this dialog
box will be modal, and control will not return to the main window until OK
is pressed in the dialog box.
Don't forget the classical Windows programmer's bug...any ideas?? Ok,
since nobody is raising their hand...it's exporting the dialog box in the
.def file. I have forgotten this so many times, I hesitate to admit it. If
you don't list the dialog box in the .def file, it is guaranteed not to
work in Windows 3.0, and will be extremely unreliable in 3.1. The next time
you get an unreliable dialog box, remember the "classical Windows
programmer's bug".
The files are archived with the magazine. I have made one other
excursion, and that is the dialog box code is in a separate file named
HELLO.DLG. The file HELLO.RC contains the following line to include the
.DLG file:
rcinclude Hello.dlg
This is pretty standard among Windows programmers, because it keeps
the dialog box code in its own file.
3.0/3.1/Borland
I just checked and found that I had produced great 3.1 code, but the
thing wouldn't run under 3.0. If you are programming for the general
public, you better keep at least a 'virgin' copy of 3.0 around on your hard
disk for this sort of checking. Users tend to get a little touchy about
that sort of thing. The fix is really easy for Microsoft people. The
problem lies in the Resource Compilation stage. If you type 'RC -?' at the
command line, you will see a '-30' switch listed. This is what will get you
- 18 -
3.0+ executables. That is the way I have it in the make file.
Borland 3.1 programmers have a couple more lines. In addition to the
RC file change, you must also add the line:
#define WINVER 0x0300
ahead of your include of windows.h in your hello.c file. Also you must
change the
wc.lpfnWndProc = WndProc; /* Name of proc to handle window */
line to be:
wc.lpfnWndProc = (WNDPROC)WndProc; /* Name of proc to handle window */
I may have left off some stuff here, if so, let me know about it.
We're all in this thing together.
Please hang in there. If you are beyond the scope of this article,
stick with me we are going to go places together. If you are way beyond
this, write us an article. If you are bogged down, just compile it, and
stare at the source. If you want help with something, send me a note.
That's it for this time. Next month I plan on building an About box
with live compile date inserted, and I'll discuss Icons and Menus. In
coming issues my intention is to discuss File Open boxes, Help files,
Dialog Boxes as main windows, Obscure Dialog Box uses, Timers, and
debugging. Feel free to contact me in any of the ways below. I want to rat
out the things other people are having questions about, not just what I
think people want to hear.
Dave Campbell WynApse PO Box 86247 Phoenix, AZ 85080-6247 (602)863-0411
CIS: 72251, 445
Phoenix ACM BBS (602) 970-0474 - WynApse SoftWare forum
- 19 -
Install Program Part III:
The SETUP.INF File
by Pete Davis
Before I get started this month, there are a couple of things I wanted
to talk about. First of all, because of some stuff coming up in the near
future, I won't be able to do Part IV of the install program next month. It
will, however, continue in May. Sorry about this, but Part IV is going to
be a big one and I'm not going to have the time to do it yet.
I'd also like to respond to Chris Newham's letter (found, oddly
enough, in the Letters section) regarding installing DLLs which are
currently active. I have to admit I haven't yet tried this, but plan, by
Part IV to have a solution. I went through Microsoft's code for their
install program and I couldn't find anything that seemed to make exceptions
for DLLs. This leads me to believe that the solution is something fairly
simple. If worst comes to worst, you could always just find out who's using
it and shut them down. I doubt this is a very good way to do it, but it's
just a thought. Like I said, I'm going to look into it and I can hopefully
have a solution to it by Part IV. (I better have a solution by then, 'cause
Part IV is going to handle copying the files over.)
Ok, this one's going to be short and sweet. There's not much to it.
We're using a SETUP.INF file which is going to tell us what the name of our
application is, how big it is, what files have to be installed, what disks
they're on, whether or not they're executables, etc. I had two options for
doing this. I could have used the GetPrivateProfileString and
GetPrivateProfileInt functions and make it into a .INI file, but I wanted
to maintain Windows 2.0 compatibility. (Just kidding :-) Actually, one
reason I didn't is because I didn't think of it until it was too late.
Actually, there are some problems with that approach. The problem is that
we're dealing with multiple files and they're going to have the same entry
names. I'm sure you understand completely now, right? Ok, here's an example
of a SETUP.INF file and then I'll explain it again.
; Semicolons, as is typical for these kinds of files, mean comments follow
; and the line is ignored.
Application=My Application
AppSize=1204558
DefaultDir=\MYAPP
;
; Now we'll have information about each file
;
; CompName = Name of file compressed on install disk
; UCompName = Name of the file when we uncompress it.
; FileType = 0 - EXE 1 - DLL 2 - Other (Other is the default)
; FileSize = Uncompressed file size
; AddDir = Sub-directory name if it's a sub-dir of the DefaultDir
; (i.e. AddDir=\DATA would mean the file is in \MYAPP\DATA
CompName=MYAPP.EX0
UCompName=MYAPP.EXE
FileType=0
- 20 -
FileSize=10294
CompName=DATA1.DA0
UCompName=DATA1.DAt
FileType=2
AddDir=\DATA
Ok, that should be enough for a sample. Now our code is going to start
a separate node in our linked list of files to install each time it hits a
CompName= statement. This is harder to do with the GetPrivateProfile...
functions. We could throw in a new section like [FILE1] for the first file,
[FILE2] for the second, etc. But back to the topic, we're not doing it that
way. I just wanted to give you ideas of how it could be done if you choose
to do it that way.
All right, well, that was all simple enough. Did I mention linked list
in the last paragraph? Yup, that nasty phrase!!! Ok, it's pretty simple
linked list. To make it easier, it's essentially a stack, so each time we
get a new file, we just add it to the front of the list. The code is in the
READSET.C file and the FILES.H file. I've commented it pretty heavily, so I
won't go in depth here. It's all very simple.
In Part IV, which will be in May, we're going to do a lot of the real
work. Like I said, it's going to be a big one, and we're going to be tying
in all the stuff from the first three parts. I might have to finish the
entire thing in June, just because what's left is so big. I've tried to
break this series up into easily recognizable parts, First I had the DDE
with Program Manager, then I had the LZEXPAND.DLL part, and this month we
had the READSET stuff. I feel like what's left all goes in the category of
'the rest of it', but there's so much, that I'll have to break it up into
two hard do divide sections. I'll probably just do a few of the small
things in each one, like the Progress Bar, creating directories, copying
files, etc...
Oh well, that's it for this month. Sorry it was so short and sorry I
can't do it next month. If I can, I might try to do all of the rest in May
to make up for not doing it at all in April. We'll see. Until next
time......
- 21 -
[Editor's Note: Last month, I started a Beginner's column on C++. Andrew
Bradnan was kind enough to offer to take over the column, and will be
writing it starting with this issue. Any questions you have can be
directed to him. His CompuServe ID is given at the end of his column. -
MW]
Home Cooking - C++ from Scratch
by Andrew Bradnan
This month I'll be starting a new column WPJ will be doing every month
to introduce people to C++ and Windows. I am going to try and keep this
simple enough for a programmer new to Windows programming and C++. Your
brain may melt if you are at this stage, but give it a try and it will
sink in after a while. Feel free to send me some email on Compuserve
[70204,63]. I'll answer any questions and put the good ones at the end of
next month's article.
I've read many books on C++ and I considered all but a few totally
confusing. (I don't plan on adding my name to this list.) There is
another rumor that C++ is really slow because it writes all sorts of code
behind your back. This is kind of like complaining that C writes all kind
of assembly behind your back. The compiler is supposed to write code for
you. The great thing about C++ is that you can forget many of the details.
I usually forget them all by myself. Instead of me rambling how great C++
is, let's find an example of exactly how we can forget some things. On
purpose. The simplest place to start is constructors and destructors. Of
all the confusing C++ terms (abstraction, encapsulation, modularity,
hierarchy, typing, concurrency, and persistence), constructors fall into
the abstraction and encapsulation category. More later. Since we almost
always want to initialize our data structures (objects) to some known
state, C++ will automatically call your own constructor every time. The
converse is also true. When you delete an object, or it goes out of scope
(falls out of a curly braces), you also want to free up memory, and set
your variables to a used state. Where in the bleep do they dome from? You
only have to do two things. First declare your member functions, and then
define them. Piece of cake. For an example, Windows is kind enough to
require us to create and destroy all sorts of stuff. There are over 63
calls to create something in the Windows API. All these objects (bitmaps,
etc.) have to be closed, deleted, freed, or destroyed (pick your favorite
synonym). Since we almost always trap the WM_CREATE message lets create a
PAINTSTRUCT object. We'll call it a PAINT. If you want to put anything on
the screen, you have to ask permission. Since Windows is kind enough to let
many programs run at once, they have to share the screen. Kind of like
kindergarten, and Windows is the teacher. To get permission you have to
ask for a display context (think constructor). Your piece of the screen.
A display context is provided when we call BeginPaint(...). Due to the
limitations of DOS and/or Windows you can only have five DC in use at once.
So after you are done you have to release the DC so that another program
can draw in his corner of the sand box (think destructor). This is done
with EndPaint(...). BeginPaint (...) also fills in a PAINTSTRUCT data
structure. The only additional information filled in is whether you should
- 22 -
paint the background, and the smallest rectangle you have to paint. In my
tradition, let's write some code that uses our PAINT object. This will
clarify what advantages we'll gain and what member functions we are going
to have to write.
//
// PAINT Object Test
// Written by Andrew Bradnan (c) 1993
// Note: The only C++ code is in the WM_PAINT case statement and in
PAINT.H
#define STRICT
#include <windows.h>
#include "paint.h"
char szAppName[] = "DC Test" ;
long FAR PASCAL _export WndProc (HWND, UINT, UINT, LONG) ;
int PASCAL WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR
lpszCmdParam, int nCmdShow) {
HWND hWnd ;
MSG msg ;
WNDCLASS wc ;
if (!hPrevInstance) {
wc.style = CS_HREDRAW | CS_VREDRAW ;
wc.lpfnWndProc = WndProc ;
wc.cbClsExtra = 0 ;
wc.cbWndExtra = 0 ;
wc.hInstance = hInstance ;
wc.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wc.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wc.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
wc.lpszMenuName = NULL ;
wc.lpszClassName = szAppName ;
RegisterClass (&wc) ;
};
hWnd = CreateWindow (szAppName, "DC Test Program",
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, NULL, NULL, hInstance, NULL);
ShowWindow (hWnd, nCmdShow);
UpdateWindow (hWnd);
while (GetMessage (&msg, NULL, 0, 0)) {
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
};
return msg.wParam ;
};
- 23 -
long FAR PASCAL _export WndProc (HWND, UINT message, UINT wParam, LONG
lParam) {
switch (message) {
case WM_PAINT:
{
PAINT Paint (hWnd); // PAINT constructor called
here!! DrawText (Paint, "PAINT Test!!", -1, Paint,
DT_SINGLELINE | DT_CENTER | DT_VCENTER); // BOTH cast operators
called! }; // PAINT destructor called here!!
return 1; // Everything Created OK
case WM_DESTROY:
PostQuitMessage (0);
return 0;
};
return DefWindowProc (hWnd, message, wParam, lParam) ;
};
As you can see we have gone from four lines of code to two. Not too
bad. The interesting part is that we no longer have to call BeginPaint or
EndPaint, and we no longer care what those pesky PAINTSTRUCT members are.
The compiler grabs it from the PAINT object for us, calling our declared
explicit cast operators. There is also sort of an intentional bug. If you
cover up only part of the client window and then bring the sample
application back to the foreground, "PAINT Test!" doesn't draw in the
middle of the client window. "PAINT Test!" will draw in the middle of the
rectangle that was covered up. Windows is kind enough to let you do a
little optimization should you want to. From the sample code above we can
determine that we need a PAINT object with four member functions. One
constructor, a destructor, a cast to an HDC, and a cast to a LPRECT.
//
// PAINT Object Header
// Written by Andrew Bradnan (c) 1993
//
#ifndef __PAINT_H
#define __PAINT_H
#ifndef __WINDOWS_H
#include <windows.h>
#endif
class PAINT {
public:
PAINT (HWND p_hWnd) : hWnd (p_hWnd) {BeginPaint (hWnd, &ps); };
~PAINT () { EndPaint (hWnd, &ps); };
operator HDC () { return ps.hdc; };
operator RECT FAR * () { return &ps.rcPaint; };
operator BOOL () { return ps.fErase; };
- 24 -
protected:
HWND hWnd;
PAINTSTRUCT ps;
};
#endif // __DC_H
As you can see, strangely enough all the code is written in the header
file. This is called inlining. The short story is that this allows the
compiler to optimize your source code. It does this by replacing the
function call with the code you have written. So our call to DrawText ()
really won't call two functions to get the parameters it will just
reference the members in our PAINTSTRUCT ps. It is essentially like using
a macro except you get all the type checking thrown in for free. Some
statement will not inline but your compiler will let you know what these
are. You will also note that the constructor looks a little weird. In the
constructor, we can optionally tell the compiler how to initialize our
members hWnd and ps. If we do not the compiler will create them, set all
the members to zero, and then we would initialize the member within the
curly braces. Obviously one more step than you want. To initialize hWnd
we use the parameter passed in. Memory for hWnd is allocated and then
filled with the parameter passed in. Which is exactly what we wanted.
Space for ps is allocated, set to zero, and then we initialize it, using
BeginPaint, in the function body. An extra step but it can't be avoided in
this case. So the moral of the story is that C++ code can be quite easy
to read when you are using the object. Writing the actual code is a little
messier. Just remember you only have to get it right once. You can forget
two function calls and three PAINTSTRUCT member names. You can even forget
about PAINTSTRUCT. Readers familiar with some of the brand name
applications frameworks may be wondering why I did not add DrawText() as a
member function to our PAINT object. Doing this we could just remove the
two cast operators and call DrawText with new parameters - DrawText (LPSTR
szText, int cb, UINT fuFormat). The only problem is we have to learn more
than we forget. Now you have two versions of DrawText(). This is supposed
to be easier not more complicated. The second reason is that you can't
DrawText a PAINT. English wise, this makes no sense. You can draw some
text on a DC. That would make sense. You would also end up with every
drawing function listed under the PAINT and/or DC object. Definitely
confusing. Just look at Microsoft's AFX. Yuck! I have built the above
example using BCW 3.1. With minimal changes it ought to work fine with MSC
7.0 and with Windows NT. Next month I'll start a little earlier so I can
test it on all three platforms and send the appropriate make files to make
your life easier. Again if you have any problems, questions, suggestions,
or answers send me a note on Compuserve [70204,63].
Andrew Bradnan is president of Erudite Software, Inc. Their latest
offering, which he wrote using C++, is called Noise for Windows, a sound
utility for Windows 3.1. Feel free to contact him for information.
- 25 -
Creating And Using Owner Draw Buttons
By Todd Snoddy
Many of you may be wondering how some Windows programs display the
fancy bitmapped buttons. Although these are not standard controls, Windows
does provide the capability to utilize controls that are custom designed.
Many times a properly displayed graphic can mean much more than a simple
text word. I will try to explain how you can use your own custom buttons
in your programs.
My sample code is written in Turbo Pascal for Windows, although the
techniques apply just the same to Turbo C++ for Windows. I use Borland's
Object Windows Library in my examples, but if you use Microsoft Foundation
Classes it should still be possible to understand the basic concepts.
My code emulates some of the functions of Borland's BWCC.DLL, which
many people use to enhance the visual appearance of their programs. This
DLL provides an assortment of functions that can give your program that
extra visual 3D look without too much work on your part. Unfortunately,
the size of the DLL can be a big factor in deciding whether or not to use
it, especially if you are writing a shareware program and want to keep its
size down. You may also have your own reasons for not wanting to use the
DLL, and this may cause you to look for other solutions.
I will demonstrate how to use what is called owner drawn controls. My
examples show how to simulate the BWCC style buttons from BWCC.DLL using
owner drawn buttons. If you want to use owner drawn buttons in your
programs, it should be rather straightforward to use my code "straight out
of the box" or modify it to suit your needs.
We'll start with the obvious question. What are owner drawn controls?
An owner drawn control is a special type of control which is designed to
allow the program to specify custom behavior. It is most often used for
listboxes with graphics or for custom buttons.
Whenever a user clicks on an owner drawn control or it changes state
in some other way, like getting the focus, a special message is sent to the
owning window of this control. This message is called WM_DRAWITEM, and its
purpose is to let the window know that one of it's owner drawn controls
needs attention. Along with this message, a pointer to an information
structure is sent to the window. This structure contains information about
what exactly happened with the control, and which control it was. The
pointer to this structure is passed in lParam. In Pascal format, the
structure looks like:
TDrawItemStruct = record
CtlType: Word; { Control type, can be odt_Button, odt_ComboBox,
odt_ListBox, odt_Menu}
CtlID: Word; { ID of Control }
itemID: Word; { Not used for buttons. Has different
meanings for other types of controls }
itemAction: Word; { What happened. For buttons, tells if gained or
- 26 -
lost focus, or selected }
itemState: Word; { What state control should be in after this
drawing. Buttons only use ods_Disabled, ods_Focused, and ods_Selected }
hwndItem: HWnd; { Window handle for the control }
hDC: HDC; { Display context to be used for drawing the control }
rcItem: TRect; { Defines clipping boundary rectangle for control }
itemData: Longint; { Not used for buttons. Only used for listboxes
and comboboxes }
end;
The owning window can examine this structure and determine what needs
to be done with the control. By looking in the CtlType field, it will know
what type of control the message is for. For a owner drawn button, this
will be odt_Button. The CtlID field contains the ID of the control. The
itemID field is not used for owner draw buttons. The itemAction field
tells what happened with the control to cause this WM_DRAWITEM message. It
can contain oda_DrawEntire, oda_Focus, or oda_Select. Only oda_Focus and
oda_Select are relevant for owner drawn buttons. If oda_Focus is set, then
the focus for the button changed, and you must check itemState to see
whether or not the control gained or lost the focus. If oda_Select is set,
the selection state of the button changed, and you must check itemState to
know what the new state is.
The itemState field specifies what state the control should be drawn
in next. To check the values of itemAction and itemState, you must use the
logical AND operation since they can contain more than one value. If
(itemState AND ods_Focused) = TRUE, then the button has the focus. If
(itemState AND ods_Selected) = TRUE, then the button is selected, or
pushed.
The hwndItem field specifies the window handle for the control. You
can use this to send messages to the control's window procedure. The hDC
field is the display context that should be used when drawing the control.
The rcItem field defines the clipping rectangle for the control. It is
used mainly with owner drawn menus. The itemData field is only used for
listboxes and comboboxes.
There are a couple of ways that your window procedure can process the
WM_DRAWITEM message. It can either draw the control itself, or it can pass
the message on to the window procedure of the control. This will use an
object oriented technique and let the control draw itself instead of the
main window procedure having to worry about how to draw each control. This
is the technique that I used in my example code. The dialog window merely
passes the WM_DRAWITEM message along to the control.
The control reacts to this message by looking at the TDrawItemStruct
record and determining what state it should draw, and then draws the button
using StretchBlt. I originally wrote this to draw with BitBlt, but when
the program was tested under the 1024 x 768 resolution while using large
fonts, it became obvious that hardcoding the size of the bitmap didn't work
properly when the dialog sizes were increased. This problem has basically
two solutions. Either use StretchBlt to draw the bitmap button at a larger
- 27 -
than normal size, or have separate bitmaps depending on the resolution.
Both of these methods have their pros and cons, and in the long run I
decided to just use StretchBlt. You will notice a degradation in the
quality of the bitmaps if you do run in the high resolutions and use the
large fonts because StretchBlt can't do a perfect job scaling an image up.
That's the basic idea for using owner drawn buttons. Things will
probably be much clearer after actually looking at the source code. I'll
briefly describe how to use it.
My code is primarily designed to be used for owner drawn buttons in a
dialog box, although there's no reason why you can't use them in a normal
window. There are several steps that you will have to take to use this
code in your own programs.
1. DESIGN YOUR DIALOG. When designing your dialog, you will need to
create a standard button for each owner draw button in the dialog.
Remember what the button ID is, as that's what you'll need to know to
associate your owner draw control object to this button. You will need to
set the size of the button to be 32 x 20, which is half the size of the
actual bitmap for Borland's standard VGA bitmaps. I'm assuming that you
are using Borland's Resource Workshop to design your dialog. After you
have all of your buttons positioned where you want them and sized properly,
bring up the control attributes dialog by double clicking on each control,
and set the control type to owner draw button. After this, you won't be
able to see the button anymore, but it will still be there. Save your
dialog.
2. DESIGN THE BUTTON BITMAPS. You can use any Windows graphics program
that can save in BMP format to design the actual bitmaps. The bitmaps will
use the BWCC numbering scheme for their names. The numbering scheme is:
normal = button ID + 1000, pressed = button ID + 3000, and focused = button
ID + 5000. This means that if your button ID is 500, the bitmap number for
the normal button without focus will 1500, for pressed 3500, and for
focused it will be 5500. These are the names that you will give to the
bitmaps when you save them. There is a shareware program written by N.
Waltham called Buttons that will automate this task for you by creating all
of the necessary bitmaps from one main bitmap. It automatically adds a 3D
shadow effect and has some other useful features. This program is
available on Compuserve in the BPASCAL forum, and the author can be
contacted at 100013.3330@compuserve.com. Although you must register with
the author, I don't mind sending copies of it via Internet email to
interested users.
3. ADD NECESSARY CODE TO YOUR PROGRAM. You will need to add OwnDraw to
your USES statement in your program. You will also need to make your
dialog object a descendant of TOwnerDialog. The sample program shows an
example of doing this. The last thing to do will be to use the NewButton
method in your dialog's constructor for each owner draw button in that
dialog. The NewButton method is called with the button ID, and a Boolean
True or False depending on whether or not you want that button to be the
- 28 -
default button in the dialog. If this is True, then it will be the button
drawn with focus when your dialog is initially created.
That's all there is to it. When your dialog is displayed, the owner
draw buttons will be displayed along with it. Of course, to get the
buttons to do some useful function, you will need procedures in your dialog
object to respond to each button selection. The sample program
demonstrates this too.
As you can see, using bitmapped buttons in your programs is not quite
as difficult as it may at first look. When properly used, bitmaps can
really make a big difference in the look of your program.
I welcome any comments you may have, good or bad. I can be reached on
Compuserve at 71044,1653, on America OnLine at TSnoddy, and on the Internet
at tsnoddy@nyx.cs.du.edu.
- 29 -
Hacker's Gash
by Mike Wallace and Pete Davis
This is our first attempt at a tips/tricks column. If you couldn't
figure out what the title meant, blame Pete. Here are three tricks we've
come up with. Hope you like them. If you have any you want to share with
our readers, send them in! Full credit will be given for all tips we
publish, of course.
1) Menu bar on a dialog box: We spent a lot of time on this one, but the
solution turned out to be a simple one. In the dialog box definition in
either (a) the .RC file, or (b) a .DLG file you include in the .RC file,
throw in the lines:
STYLE WS_OVERLAPPEDWINDOW
MENU <menu name>
where <menu name> is a menu you have defined in the .RC file. The style
WS_OVERLAPPEDWINDOW combines WS_OVERLAPPED, WS_CAPTION, WS_SYSMENU and
WS_THICKFRAME, and is a standard parent window.
2) Highlighting a button on a dialog box when the cursor is over it: By
highlight, I mean the button moves "in" slightly, and it's a cool effect
that can look pretty impressive. Add the following declarations to the
function that controls the dialog box:
static BOOL ButtnDn;
static int LastDown;
static int IDNumber=0;
int counter;
Next, assume you have 5 buttons on your dialog box, and these buttons are
identified by the constants IDB_BUTNx (where x is a number between 1 and
5), which are defined in the .H file for the .DLG file containing the
definition for your dialog box. Also assume these five constants are
defined sequentially, say, 101 to 105. The variable "hDlg" is the handle
to the dialog box, and "message" is the message passed to the function by
Windows (these are, respectively, the first and second parameters passed
into a standard window-handling function). Add the following two cases
inside the "switch(message) {}" structure:
case WM_SETCURSOR:
ButtnDn= TRUE;
LastDown= IDNumber;
IDNumber= GetDlgCtrlID(wParam);
for(counter=IDB_BUTN1; counter<=IDB_BUTN5; counter++)
if(counter==IDNumber)
SendDlgItemMessage(hDlg, counter, BM_SETSTATE, TRUE, 0L);
if(IDNumber != LastDown)
SendDlgItemMessage(hDlg, LastDown, BM_SETSTATE, FALSE, 0L);
- 30 -
break;
case WM_NCHITTEST:
if (ButtnDn) {
ButtnDn = FALSE;
SendDlgItemMessage(hDlg, LastDown, BM_SETSTATE, FALSE, 0L);
}
break;
3) Using DlgDirList for a subdirectory: I recently tried using the
DlgDirList function passing a pathname like "DATA\\*.DAT" to fill a list
box with all the files in the DATA directory ending with .DAT (by the way,
the double backslash ("\\") is needed because the backslash is a special
character in a C string). Afterwards, I found out that if this function is
successful, it changes the current working directory to whatever is
specified in the string (here, the "DATA" directory). Afterwards, I tried
changing the directory back to the original working directory, but if the
function got called again, chaos ensued. I managed to get around this by
including in the program the following line:
#include <direct.h>
The direct.h file has prototypes for the directory-manipulating
functions, including the one needed here: chdir, which changes the current
working directory. I replaced the call to DlgDirList with the following
code:
chdir("DATA");
DlgDirList(hDlg, "*.DAT", IDL_POPUP, IDL_DIRSTRING, 0);
chdir("..");
The "hDlg" variable is the handle to the dialog box, "IDL_POPUP" is
the dialog box ID value for the list box control, "IDL_DIRSTRING" is the
dialog box ID value for a static text control that will be updated with the
current path name, and 0 is the DOS file attribute used to retrieve a list
of read/write files with no other attributes set. There are other values
you can use for the DOS file attribute value to retrieve, for example,
subdirectories and drives. This code simply changes to the DATA directory,
gets a list of the .DAT files in that directory and puts that list in the
IDL_POPUP list box and the path in the IDL_DIRSTRING static text control,
and then changes back to the parent directory. You can use the
DlgDirSelect function to retrieve a selected filename from the list box.
- 31 -
Special News
by Mike Wallace
We recently got a letter from Todd Snoddy offering to put WPJ on the
America Online information system. We said "Sure", and a couple of days
later got a phone call from a sysop for the board. He liked the magazine
(all contributing authors can now pat themselves on the back) and asked how
we would feel about holding an on-line conference on AO, giving readers a
chance to talk directly to us and ask us any questions they have about the
journal. Sounded good to us, so we agreed, and now hope to bring this to
you AO subscribers soon. The details are still being worked out, so watch
the AO Programming Library for updates.
For the benefit of our readers without access to America Online, I'll
try to write down what questions get asked and our answers and include them
in our next issue.
Thanks, Todd, and the great folks at America Online!
- 32 -
Windows 3.1: Using Version Stamping library
By Alex Fedorov
Windows 3.1 introduces the new resource type with ID number 16 called
VS_FILE_INFO. This resource contains the symbolic data about the file
version, its description, the company name and so on. Such information can
be used to determine the file type/subtype and its version and can be used
in installation programs. Resource data lives as a set of blocks; each
block contains information of its own. The first one (with a fixed size)
describes the TVS_FixedFileInfo structure. The fields of this structure
contain the following information: file version, environment type and
version, file type and subtype. There are several subblocks, which contains
symbolic information with the following IDs (the table contains only those
which must present):
ID Contents
CompanyName Company name
FileDescription File description
FileVersion File version
InternalName Internal file name
ProductName Product name
ProductVersion Product version
Resource of this type can be created with resource editor, such as
Resource Workshop (version 1.02 or higher), or with resource compiler (RC
or BRC). The latter needs the resource template. VS_FILE_INFO resource
for resource compiler looks like the following:
1 VERSIONINFO LOADONCALL MOVEABLE
FILEVERSION 3,10,0,61
PRODUCTVERSION 3,10,0,61
FILEFLAGSMASK VS_FF_DEBUG | VS_FF_PATCHED
FILEFLAGS VS_FF_DEBUG
FILEOS VOS__WINDOWS16
FILETYPE VFT_APP
FILESUBTYPE VFT2_UNKNOWN
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904E4"
BEGIN
VALUE "CompanyName", "Microsoft Corporation\0"
VALUE "FileDescription", "Windows Terminal application
file\0"
VALUE "FileVersion", "3.10.061\0"
VALUE "InternalName", "Terminal\0"
- 33 -
VALUE "LegalCopyright","Copyright \251Microsoft Corp.1991\0"
VALUE "OriginalFilename", "TERMINAL.EXE\0"
VALUE "ProductName","Microsoft\256 Windows\231 Operating
System\0"
VALUE "ProductVersion", "3.10.061\0"
END
END
END
To get access to VS_FILE_INFO resource data you can use the functions
from VER Dynamic Link Library included with Windows 3.1.
The example below shows how to use some of those functions.
{------------------------------------------------------
FileVer: An example of VER.DLL functions usage
(c) Alex G. Fedorov, 1993
------------------------------------------------------}
Program FileVer;
uses WinCrt,WinTypes,WinProcs,Ver,Strings;
Const
{Any file name here}
FileName = 'C:\WINDOWS\SYSTEM\VGA.DRV';
Type
PBuff = ^TBuff;
TBuff = Array[0..127] of Char;
Var
Size : LongInt;
hVer : LongInt;
VerData : Array[0..463] of Byte; {Avg. resource size}
Buff : PBuff;
NameStr : Array[0..127] of Char;
Block : Array[0..7] of Char;
Procedure ShowString(Name : PChar);
{
Show the string with description
}
Begin
{
Create query string
}
StrCopy(NameStr,'\StringFileInfo\');StrCat(NameStr,Block);
StrCat(NameStr,'\'); StrCat(NameStr,Name);
{
Query for string
- 34 -
}
If VerQueryValue(@VerData,NameStr,Pointer(Buff),Word(Size))
{
If string was found, show it
}
Then Writeln(Name,' ':20-StrLen(Name),Buff^)
End;
Begin
{
Get the resource size. hVer - Resource handle
}
Size := GetFileVersionInfoSize(FileName,hVer);
{
Get VS_VERSION_INFO block
}
GetFileVersionInfo(FileName,hVer,Size,@VerData);
{
Get the symbolic information block
The default name for it: 040904E4
The result is in Buff
}
VerQueryValue(@VerData,'\VarFileInfo\Translation',Pointer(Bu
ff),Word(Size));
{
Translate the block name
}
wvsPrintf(Block,'%04X%04X',Buff^);
If Size <> 0 Then
Begin
Writeln(^M^J'Version information for ',FileName);
Writeln;
{
Query for the following IDs
}
ShowString('CompanyName');
ShowString('FileDescription');
ShowString('FileVersion');
ShowString('ProductName')
End;
Readln; DoneWinCrt
End.
- 35 -
Book Review
by Pete Davis
Microsoft Windows 3.1 Programmer's Reference Library
If you're a serious Windows programmer, you've probably got
Microsoft's series of books on Windows programming. The series is broken up
into 4 volumes and two extra books, as follows:
- Volume 1: Overview
This is, as it says, an overview. It covers a lot of different topics
from how windows are managed, graphics, and how to write extensions for
Control Panel and File Manager.
- Volume 2: Functions
This is an alphabetical listing of all the Windows 3.1 functions. It's
a big one.
- Volume 3: Messages, Structures and Macros
Like the previous two, this one is exactly what it says it is. it's
got some great information on all the structures, which comes in real
handy.
- Volume 4: Resources
This one has a lot of information on file formats for a bunch of
different Windows files (.GRP files, Meta Files, etc.) It's also got a bit
of information on creating Help files, using assembly language in Windows,
and more.
- Programming Tools
Not listed as a Volume. This is more of an additional Microsoft C
reference. It covers a lot of the SDK tools, debugging with Codeview, data
compressions and that kind of stuff.
- Guide to Programming
Also not listed as a Volume, but I would have made it one. It covers
different types of resources like Icons, Dialog boxes, etc. Printing, DLLs,
MDI, Fonts, Assembly and C, and more.
Ok, so there's our list. I would say, all-in-all, this is probably the
most definitive resource on programming for Windows and that anyone
planning on doing a lot of Windows programming should get the whole series.
It's a big ticket item. The least expensive book is $23 (US) and the most
expensive is $39 (US).
I suggest you get some other books and not limit yourself to just
these. Although they cover most of the topics most programmer's will need,
the books are lacking in some areas and there are some typos which have
slowed this programmer down on more than one occasion. Every book has typos
and that's why, as a serious programmer, your library should come from
several sources so you can check the books against each other when you run
into problems.
- 36 -
These books are, however, well worth the cost. There's a lot of stuff
covered in these books that you won't find in other sources. The detail on
different file formats, for example, I haven't seen in any other books. The
structures list is very complete and is a very handy reference. There's
also some good information on memory management and DLLs.
What these books aren't
These books aren't everything and, like I said, don't limit yourselves
to just these. The Windows API bible (which Mike will be reviewing) has
some great information too and it's a good one to check against Microsoft's
Function reference when you get suspicious that a function isn't doing
something it's supposed to do. Also, because it's Microsoft, you're not
going to get the wealth of inside, under-the-hood, information like you get
from Undocumented Windows (Andrew Schulman, David Maxey, Matt Pietrek.
Addison-Wesley. See the review in WPJ Vol.1 No.1)
Unfortunately, because Windows is such an enormous system and no
single book can even begin to cover every aspect of it, we programmers have
to get a lot of different books. If you're serious, though, you should
consider setting aside the money to get this collection.
- 37 -
Book Review
by Mike Wallace
The Waite Group's Windows API Bible
by James L. Conger
Earlier in this issue Pete reviewed the Microsoft Windows 3.1
Programmer's Reference Library. Now it's my turn to review my Windows
programming reference book of choice: The Waite Group's Windows API Bible
by James Conger. Pardon my French, but this book kicks serious "lune".
While the Microsoft books spread out everything across several books, the
Waite Group threw it all into one book, making anything you want to look up
easy to find. This book is a must-have for anyone wanting to learn how to
program Windows. Here are the details:
The book is divided into 30 chapters, each discussing a distinct area
(e.g., creating windows, menus, windows messages, metafiles). Each chapter
starts off with a discussion of its topic (usually several pages long),
then a summary of the associated functions (a list of the function names
with a one line purpose), and then a complete description of the same
functions in the summary. Their descriptions are detailed and include
suggested uses for the function, a great "see also" reference list (more
complete than the Microsoft books), an explanation of all the parameters
and the return value, and code (real programs) that shows how to use the
function. It's my one stop for looking up a function. Plus, the inside
front cover contains a list of all the API functions with a page number so
I can quickly jump to the function I want to look up, followed by a list of
messages grouped by category (e.g., button notification codes, windows
messages).
But wait, there's more...the book also includes a pullout reference
card containing a list of all functions with the type declarations for all
parameters and the return value, plus the same list of messages that's on
the inside cover. The function list in the pullout card is organized the
same way as the chapters, so that all the functions listed in the chapter
on, say, sound functions, are grouped together in the pullout under the
heading "Sound Functions." I couldn't ask for a better list. In the
Microsoft books, you have to know the name of the function before you can
look it up, and there have been many times when I didn't know what the
function was called, but I knew what it did. This book lets me find the
name of the function quickly, and then shows me how to use it.
In the appendices, there is a discussion of useful macros from
WINDOWS.H, mouse test hit codes, and a printout of WINDOWS.H, which has
been very helpful to me on occasion. The book ends with a very complete
index, making it extremely easy to look up related subjects.
There are very few faults with this book, but there a few. One is the
strange absence of the Windows API functions starting with the letter "N".
I am not making this up. The Microsoft reference book on functions lists
four functions starting with "N": NetBIOSCall, NotifyProc, NotifyRegister
and NotifyUnRegister. None of these functions are in the API Bible. There
- 38 -
is also no discussion of OLE or True Type fonts, although the author writes
(in the introduction) these will be covered in a separate volume currently
under development.
Also, the Microsoft volume on programming tools (those included with
the SDK, such as Spy) covers an area not addressed by the API Bible. If
you want to use the Microsoft SDK, the API Bible won't be of much help, but
this isn't much of a fault, because Microsoft isn't the only producer of
Windows SDKs, and you're going to get manuals for any SDK you buy, so why
include a lot of information that some readers won't need to know?
To me, the faults of this book do not detract from its overall
usefulness. It has paid for itself many times over, and when compared to
the Microsoft reference books, the price seems insignificant. Total price
for the six books in the Microsoft Windows 3.1 Programmer's Reference
Library: around $170-180. The Windows API Bible: $40. I recommend this
book. You'll thank me for it.
- 39 -
Printing in Windows
by Pete Davis
Ok, I failed to get this article out last time and I've been feeling
really guilty about it, so here it is. Writing this article hasn't been
easy. Writing about printing reminds me of my first attempt at printing
under Windows. It wasn't a very good one.
Actually, printing under Windows isn't a big deal and if you're
printing graphics, it's a lot easier than you'd think. My real problem with
printing the first time was incomplete/incorrect documentation. I won't
mention names, but I saw at least 3 examples of printing in different books
on Windows programming and every one of them either misled me or had code
that didn't work.
Here's the deal. When you print, you MUST, and I mean MUST (I'm really
serious about this), have an abort procedure. This is an absolute
necessity. You MUST have it. You can't do without it. You'll have problems
if you don't have an abort procedure, so write one. Are you starting to get
the picture here? An abort procedure is NOT optional, you MUST have it.
Now, you might be wondering why I'm emphasizing this point. Every book
I read on printing made it sound like an option. I needed to print one page
of text (and not much text at that) and didn't have a need for an abort
procedure, so I didn't include one. I kept getting UAEs as soon as I tried
to print. It even occurred to me that I might need the Abort procedure, and
I specifically looked for a phrase which said something along those lines
in every bit of documentation I had. Finally, 4 days later, I decided I'd
just pop an abort procedure in. (I had tried just about everything else at
that point, why not?) Of course, it worked, and I had wasted four days
because I HAD TO HAVE AN ABORT PROCEDURE!!!!
So, I'm assuming that, after reading this, none of you are going to
spend four days trying to figure out why your print routine UAEs only to
find that you forgot your abort procedure. If I hear of any of you doing
this after reading this article, I'm going to come over and personally give
you a whuppin (as we say down in Arkansas). If you haven't, at this point,
realized that an abort procedure might possibly be a good idea to include
in your print procedure, you fit into one of the following three
categories:
1> You program on the cutting edge of Atari 2600 technology.
(or some other cutting edge of obsolescence)
2> Blind and not able to read any of this anyway.
3> Just plain stupid
There, so I have that off my chest, on to the meat of this article,
which is how to print. It's real easy!!!
The first thing we'll look at is our Abort procedure. (Did I mention
that you need this part?) The abort procedure is real simple. It receives
the device context of the printer and an error code. In our example, the
- 40 -
nCode is the error code and if it's non-zero, you have an error.
Basically all your abort procedure has to do have a message loop. I
don't do anything else with it, myself. It should look something like this.
BOOL FAR PASCAL AbortProc(HDC hdcPrint, short nCode) {
MSG msg;
while(!bPrintAbort && PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
if(!hDlgPrint || !IsDialogMessage (hDlgPrint, &msg)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return !bPrintAbort;
}
The next thing on our agenda is the PrintDlgProc procedure. This is
basically the dialog box procedure for our cancel printing dialog box.
Although there is a cancel button in our dialog box, instead of checking
specifically for the cancel button, our only concern is with a WM_COMMAND.
(We only have one button, so what other WM_COMMANDS are they going to be
sending?) If we get the WM_COMMAND, we basically abort the printout (by
setting bPrintAbort = TRUE) and destroy our dialog box. Pretty darn simple.
BOOL FAR PASCAL PrintDlgProc(HWND hDlg, WORD message, WORD wParam, LONG
lParam)
{
if (message == WM_COMMAND) {
bPrintAbort = TRUE;
EnableWindow(GetParent(hDlg), TRUE);
DestroyWindow(hDlg);
hDlgPrint = 0;
return TRUE;
}
return FALSE;
}
The next procedure is our printing procedure. There are a couple of
neat things here that I'd like to talk about. First of all, there's the
four lines involved with getting information about our printer driver. What
we're doing here is just getting the default printer device. We get the
information via a GetProfileString command. Under the section "windows" and
then the line starting with "device". That's our printer. All the
information goes into szPrinter which we then break into the Device,
- 41 -
Driver, and PortName using the strtok function. This basically breaks out
our string into separate strings. The second parameter of strtok is the
thing we want to break our string apart at. In this case, we want it broken
up at the commas. Also, notice how we only pass szPrinter once. If we use
NULL in the next two occurrences of strtok, it knows to continue using
szPrinter and to continue from where we left off which, in this case, is at
the last comma.
GetProfileString("windows", "device", ",,,", szPrinter, 80);
lpDevice = strtok(szPrinter, ",");
lpDriver = strtok(NULL, ",");
lpPortName = strtok(NULL, ",");
Our next job is pretty simple, we just create a device context based
on the information about our driver.
/* Create the device context. */
if ((hdcPrint = CreateDC(lpDriver, lpDevice, lpPortName, NULL)) == NULL) {
MessageBox(hWnd, "Could not assign printer.", "ERROR", MB_OK);
return FALSE;
}
Here's another thing you don't really have to deal with in DOS. With
Windows, you need to know where you are on the page. The reason is that you
have to tell Windows when you're done with the current page. This means you
need to know the size of a page and the size of the text on the page. Now,
this can be a bit of a pain, but it also allows for a lot of interesting
stuff. For example, you don't have to start from the top of the page and go
down. You can print the bottom part of the page, and then print something
in the middle of the page, and so on. Then you just tell Windows to eject
the page. In our case, we're going to be assuming 1 page or less of text
and not really deal with that. (As they say in school books, this is left
as an exercise for the reader.) All we're going to do is use the text size
to tell us where to print the next line of text. First we have to use
CurYPos as our current Y position on the page. (I love descriptive variable
names.) Then we need to find out how tall a single character is. That's
done from getting a text metric for the printer and adding the character's
height, plus the external leading, which is kind of like the blank space
between lines.
CurYPos = 1;
GetTextMetrics(hdcPrint, &tm);
yChar = tm.tmHeight + tm.tmExternalLeading;
The next step is to create our dialog box and abort procedure. By the
way, the abort procedure is not optional.
lpfnPrintDlgProc = MakeProcInstance (PrintDlgProc, hInst);
hDlgPrint = CreateDialog(hInst, "PrintDlgBox", hWnd, lpfnPrintDlgProc);
- 42 -
lpfnAbortProc = MakeProcInstance(AbortProc, hInst);
bPrintAbort = FALSE;
Now, the secret to printing is all in the Escape commands. (Actually,
in Windows 3.1, they have commands you can use instead of Escape, but since
we try to maintain our 3.0 backwards compatibility, we're going to use
Escape.) The Escape is the heart of the printing routines. You use it to
send different commands and/or data to the printer driver. The first one
we'll send is our SETABORTPROC command. This sets up our all-important
abort procedure.
Escape(hdcPrint, SETABORTPROC, 0, (LPSTR) lpfnAbortProc, NULL);
The next Escape we're going to send is our STARTDOC command. We're
going to pass the name of our document and the length of the string that
has the name of our document. Pretty straight-forward.
Escape(hdcPrint, STARTDOC, strlen(szMsg), (LPSTR) szMsg, NULL);
Since our example involves reading from a file, we're just going to
read one line at a time from the file. We use a TextOut function to output
our string to the printer driver. We need to give X and Y coordinates. In
our case, the X is always 0 and the Y position is calculated from the
GetTextMetric which we did above. We just add the text height to the
current Y position after each line.
while (fgets(CurLine, 132, inFile)!=NULL) {
TextOut(hdcPrint, 0, CurYPos, CurLine, strlen(CurLine)-2);
CurYPos += yChar;
}
When we're done, we need to send a NEWFRAME command via the Escape
function. This basically means to advance to the next page. Now, if you're
printing multiple pages, you need to do a NEWFRAME between each page. In
our case, we're only allowing one pages, so we do it right at the end.
Escape(hdcPrint, NEWFRAME, 0, NULL, NULL);
The last thing to do is send an ENDDOC. This basically tells the
printer driver we're done and it can begin printing the document.
Escape(hdcPrint, ENDDOC, 0, NULL, NULL);
After all that, we just close the file and delete the device context
for our printer, then get a cup of coffee and a smoke and relax.
fclose(inFile);
DeleteDC(hdcPrint);
Ok, so it's a little more complex than printing text under DOS, but
the thing I didn't mention is the graphics. Now, I'm not going to go into
great detail about it, because it is a little more complex, but not much.
- 43 -
Essentially, all you have to do is route all of your graphics to a printer
device context instead of a screen device context. The complexity comes in
when you're trying to figure out pages sizes and that kind of stuff, but
for the most part, printing graphics is as easy as printing text.
There are some things we didn't cover here. One of them is a thing
called banding, which is a way of printing your page in parts called bands.
This is particularly useful for dot-matrix and other non-postscript
printers. Banding makes it a bit faster to do the printing. Some printer
drivers have the banding built-in and I, personally, have never had to work
in an environment where printing had to be particularly fast, so I've
avoided banding. If someone feels that banding is of particular importance,
they're more than welcome to write an article on it.
That just about wraps up printing. It's really not all that complex,
and as you can see, Windows gives you a lot of power as to how to handle
it. There are all kinds of neat and nifty things you can do, like mixing
graphics and text, which is a cinch. Try doing that in DOS. And last but
not least, please, don't forget your abort procedure.
- 44 -
Advanced C++ and Windows
By Andrew Bradnan
Talking to the User
Overview
While Windows offers a great variety of ways to talk to the user, none of
them are very easy. Even fewer are quick to implement. In this article we
will look at some C++ classes to make things a little easier.
Output
Introduction
Let's look at a few ways to talk to the user. The simplest way to tell the
user something is to create a message box. This is one of the easiest
things to do in Windows. Luckily, Windows does most of the work for us.
We are going to make it even easier. With the classes we are going to
create you will never have to remember what the parameters are, what order
they go in, and exactly how they spelled all the constant values you can
pass in and that MessageBox() passes back. We also need a good way to tell
the user and ourselves about error messages (God forbid).
Message Boxes
If you are at all familiar with the MessageBox () call you are well aware
that there are several different incantations. Each has its own purpose
and can be used for several circumstances. Its function prototype looks
like this: int MessageBox (HWND hwndParent, LPSTR lpszText, LPSTR
lpszTitle, UINT fuStyle); You can pass it 18 different flags (or
combinations) and it will return seven different flags to tell you what the
user did. Not as ugly as CreateWindow() but not easy either.
OKBOX
A stream is an abstraction referring to the flow of data from a producer to
a consumer. The first output object we are going to create is an OKBOX.
Once again it does exactly what it sounds like. It displays a message box
with one "OK" button. The OKBOX will allow us to use C++ stream
conventions. Let's first look at how we would like to use an OKBOX so we
can write the appropriate member functions.
OKTEST.H
//
// OKBOX Test Header
// Andrew Bradnan (c) 1992
// C++ Windows Version
//
#ifndef __OBJECTS_H
#include <objects.h>
#endif
- 45 -
class WINDOW : public BASEWINDOW {
public:
WINDOW (LPCSTR lpcszClass, HINSTANCE hInstance, HINSTANCE
hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) :
BASEWINDOW (lpszClass, hInstance, hPrevInstance, lpszCmdLine, nCmdShow) {};
BOOL OnCreate (CREATESTRUCT FAR *lpCreateStruct);
};
OKTEST.CPP
//
// OKBOX Test Module
// Andrew Bradnan (c) 1992
// C++ Windows Version
//
#include <oktest.h>
BOOL WINDOW::OnCreate:: (CREATESTRUCT FAR *lpCreateStruct)
{
OKBOX msg ("OK Test Caption");
msg << "User notification."
return TRUE;
};
int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance, LPSTR
lpszCmdLine, int nCmdShow) {
WINDOW Window ("OK Test", hInstance, hPrevInstance, lpszCmdLine,
nCmdLine);
return Window.MessageLoop ();
};
We are using our WINDOW virtual function to trap the WM_CREATE
message. Here we define msg as an OKBOX with the caption "OK Test Caption"
When we want to send a message to the user we just stream some text to it.
This will invoke the message box with the "User notification." It is much
easier to read. Now that we have an easy way to send a message to the user
or ourselves let's look in the "black box" to see how OKBOX works.
OKBOX.H
//
// OKBOX Header
// Andrew Bradnan (c) 1992
// C++ Windows Version
//
#ifndef __OKBOX_H
#define __OKBOX_H
#ifndef __WINSTR_H
- 46 -
#include <winstr.h>
#endif
class OKBOX {
public:
OKBOX (LPSTR lpszCaption);
int operator<< (STRING& strMessage);
private:
STRING strCaption;
};
#endif // __OKBOX_H
OKBOX.CPP
//
// OKBOX Module
// Andrew Bradnan (c) 1992
// C++ Windows Version
//
#include <okbox.h>
OKBOX::OKBOX (LPSTR lpszCaption)
: strCaption (lpszCaption)
{};
int OKBOX::operator<< (STRING strMessage)
{
return MessageBox (NULL, strMessage, strCaption, MB_OK);
};
Short and sweet. We have a constructor that takes a string which we will
save as the caption for the message box. Our other function, operator<<()
is called by the compiler when ever it sees an OKBOX object on the left of
the << and a STRING (or an object that can construct a STRING like a LPSTR)
object on the right side.
ERRORS
Unfortunately, errors are bound to occur. We will assume these are all
going to be user errors. If we think a bit, an ERROR object is going to be
coded almost exactly like an OKBOX. In fact, let's rewrite our OKBOX
object so that we can take advantage of this. Quality if free, but we have
to pay for it up front. So that we can reuse some of the code we will
create an object call MSGBOX. MSGBOX will contain the code shared by the
OKBOX and the ERROR object.
MSGBOX.H
//
// MSGBOX Object Header
- 47 -
// Andrew Bradnan (c) 1992
// C++ Windows Version
//
#ifndef __MSGBOX_H
#define __MSGBOX_H
#ifndef __WINSTR_H
#include <winstr.h>
#endif
class MSGBOX {
public:
MSGBOX (LPSTR lpszCaption)
: strCaption (lpszCaption), fuStyle (Style)
{};
int operator<< (STRING& strMessage)
{
return MessageBox (NULL, strOutput, strCaption, fuStyle);
};
private:
STRING strCaption;
UINT fuStyle;
};
#endif // __MSGBOX_H
The MSGBOX object only adds fuStyle so that different message boxes
can be created. Now we can rewrite OKBOX to use the code in MSGBOX. OKBOX
will be inherited from MSGBOX. Since we will inherit all the public
member functions, we will only have to write a short constructor.
OKBOX.H
//
// OKBOX Object Header
// Andrew Bradnan (c) 1992
// C++ Windows Version
//
#ifndef __OKBOX_H
#define __OKBOX_H
class OKBOX : public MSGBOX {
public:
OKBOX (LPSTR lpszCaption) : MSGBOX (lpszCation, MB_OK) {};
};
#endif // __OKBOX_H
The code for an ERROR object is exactly the same except for the value of
fuStyle.
- 48 -
ERROR.H
//
// ERROR Object Header
// Andrew Bradnan (c) 1992
// C++ Windows Version
//
#ifndef __ERROR_H
#define __ERROR_H
class ERROR : public MSGBOX {
public:
ERROR (LPSTR lpszCaption) : MSGBOX (lpszCation, MB_ICONSTOP |
MB_SYSTEMMODAL | MB_OK) {}; };
#endif // __ERROR_H
Great! Now we can notify the user using the OKBOX object and tell the user
about errors using the ERROR object.
Questions
Your programs often need to ask the user a question. If this is a yes or
no affair we can use a message box. Let's look at how we would like to use
an object like this. Then we will write the member functions to fill out
the class.
QTEST.H
//
// QUESTION Test Header
// Andrew Bradnan (c) 1992
// C++ Windows Version
//
#ifndef __OBJECTS_H
#include <objects.h>
#endif
class WINDOW : public BASEWINDOW {
public:
WINDOW (LPCSTR lpcszClass, HINSTANCE hInstance, HINSTANCE
hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) :
BASEWINDOW (lpszClass, hInstance, hPrevInstance, lpszCmdLine, nCmdShow) {};
BOOL OnCreate (CREATESTRUCT FAR *lpCreateStruct);
};
QTEST.CPP
//
- 49 -
// QUESTION Test Header
// Andrew Bradnan (c) 1992
// C++ Windows Version
//
#include <qtest.h>
BOOL OnCreate (CREATESTRUCT FAR * lpCreateStruct)
{
OKBOX ("Question Test");
if ((QUESTION) "Do you like the object?")
msg << "Great, I hope your programming is easier."
else
msg >> "What the hell do you know!";
return TRUE;
};
int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance, LPSTR
lpszCmdLine, int nCmdShow) {
WINDOW Window ("Question Test", hInstance, hPrevInstance, lpszCmdLine,
nCmdLine);
return Window.MessageLoop ();
};
Once again we are trapping the WM_CREATE message and trying out our
QUESTION object. The code is kind of sneaky. I know one thing, you can
actually read C++ code if you write your classes carefully. I would hate
to write those four lines of code in C. They certainly would not be as
easy to read. Let's look at how the C++ compiler helps us. First of all,
when the compiler sees that we would like to cast the string to a QUESTION
it creates a temporary QUESTION object using our string. Then since the if
statement really wants a BOOL the compiler will cast the QUESTION to a
BOOL. It is in this explicit cast, operator BOOL (), that will make the
message box call. The cast member function will "look" to see if the user
hit the "Yes" or "No" button, and return TRUE or FALSE.
If you didn't understand that, let me explain it another way. The C++
compiler did all the work. Seeing as how I'm trying to champion code reuse
let's try and use the MSGBOX object. We are only going to have to write
two member functions for this object. The constructor and the cast member
function.
QUESTION.H
//
// QUESTION Object Header
// Andrew Bradnan (c) 1992
// C++ Windows Version
//
#ifndef __QUESTION_H
- 50 -
#define __QUESTION_H
class QUESTION : public QUESTION {
public:
QUESTION (LPSTR lpszOutput)
: MSGBOX ("Warning!", MB_QUESTION | MB_YESNO), strOutput (lpszOutput)
{};
operator BOOL (void)
{
if (IDYES == operator<< (strOutput))
return TRUE;
else
return FALSE;
};
private:
STRING strOutput;
};
#endif // QUESTION_H
As you can see the only action really takes place in the explicit
cast, operator BOOL (). It is here that we call the member function
operator<< () to invoke the message box call. We didn't do too much but
transform one call to the MSGBOX class. Since we declared it inline we
won't even gain any function call overhead. The C++ compiler will put this
ugly code right where our beautiful code exists right now. Pretty damn
cool.
Warnings
We also might want to warn the user of impending doom. The File
Manager uses this to confirm that you really want to overwrite a file. If
works almost like a QUESTION object except it can return whether the user
chose "Yes", "No" or "Cancel".
WARNING.H
//
// WARNING Object Header
// Andrew Bradnan (c) 1992
// C++ Windows Version
//
#ifndef __WARNING_H
#define __WARNING_H
class WARNING : public MSGBOX {
public:
WARNING (LPSTR lpszOutput)
: MSGBOX ("Warning!", MB_EXCLAMATION | MB_YESNOCANCEL) {};
- 51 -
operator BOOL (void)
{
BOOL fUserChoice;
fUserChoice = operator<< (strOutput);
if (fUserChoice == IDYES)
return TRUE;
else if (fUserChoice == IDNO)
return FALSE;
else // if (fUserChoice == IDCANCEL)
return -1;
};
private:
STRING strOutput;
};
#endif // WARNING_H
Wondrous.
Why, you ask, go through all this trouble just for a MessageBox()
call? Well, there are plenty of reasons. Let's review them. First, who
can remember all those flag variables? Not me. Does the caption go first,
like I know it should (and always write)? No! No matter how many times I
write it the correct way, Windows just will not learn. Second, the folks
at Microsoft just love to change things. The MessageBox() call may gain
new functionality. Why romp through all the code you have written to
change it for the latest greatest MessageBoxEx()? It would be a pain and
you surely would miss some of your old code. Now all you need to do is
make a change to the MSGBOX class. Third, you can add all sorts of
checking to the base class. This really doesn't apply in this case but in
more complicated classes this will be important. We even gain some
checking from the string class. "How?" you ask. There is nothing to screw
up with constant strings. I'm afraid not, my friend. Once upon a time, I
forgot to export one of my functions (good thing you have never done that)
that used one of these classes. My data segment was pointing off to never
never land and luckily my STRING's told me as much. They didn't even GP
fault, just quietly told me that I had screwed up. Windows 3.1 has brought
multimedia to our clutches so let's look at a real example of how powerful
C++ inheritance can be. We will be updating MSGBOX.
MSGBOX.H
//
// MSGBOX Object Module
// Andrew Bradnan (c) 1992
// C++ Windows Version
//
#ifndef __MSGBOX_H
#define __MSGBOX_H
#ifndef __WINSTR_H
- 52 -
#include <winstr.h>
#endif
class MSGBOX {
public:
MSGBOX (LPSTR lpszCaption);
: strCaption (lpszCaption), fuStyle (Style)
{};
int operator<< (STRING& strMessage);
{
MessageBeep (fuStyle);
return MessageBox (NULL, strOutput, strCaption, fuStyle);
};
private:
STRING strCaption;
UINT fuStyle;
};
#endif // __MSGBOX_H
You have now added multimedia to everywhere you use message boxes.
All by changing one measly line. You're a fast worker. Your boss will
probably give you a raise. We have just implemented some easy ways to have
dialog with the user. They are by no means complicated, but just think of
the complicated things you can make this easy, even fun to program. There
are plenty of other nasty constants you can play with to create you own
classes. Then you can rip those pages right out of the Windows
Programmer's Reference.
- 53 -
The Trials and Tribulations of an Antipodean Abecedarian who uses Turbo
Pascal to Program Windows
Part 1
They Also Serve Who Only Stand and Wait
Jim Youngman
[CompuServe 100250,60]
I live in Melbourne, Australia and I work in a one man Operations
Research department. The programming that I do is in order to solve OR
problems that I encounter. I am very much isolated in my job although I do
my best to keep in touch with others through professional societies and
computer user groups. e-mail has proved invaluable since I had a modem
installed a short time ago.
The problems I have struck are often simple in the end, but they can
be confusing for a beginning Windows programmer such as myself. The
documentation for the various programming languages does not tell you what
you need to do; rather it tells you how to do something when you know what
it is you need to do. Perhaps sharing some of these trivial trials and
tribulations in a series of short articles might just help another
beginner.
I have the job of writing a user friendly front end to a mathematical
programming package. The end users will be line managers with a background
in mechanics rather than computers. This led me to decide that the best
medium for them to use the final application would be Windows (we are
standardized on IBM compatible machines).
One of the first things I needed to do after setting up various
screens using the Borland Resource Workshop was to find out how to run the
mathematical programming package from within a Windows program. The
package is DOS based and uses the Phar Lap DOS extender to run in 32 bit
mode.
How could I do this? I could not find any clues in the manuals, nor
in the only book on TPW that I had at the time: Tom Swan's Turbo Pascal for
Windows 3.0 Programming (1991) which is an excellent introduction to the
subject.
The books being no help, I then went on to search the Borland Pascal
Help file. Eventually I tried a search on Load and soon discovered the
function LoadModule. This was described as loading and executing a Windows
application, but there was a cross-reference to WinExec. The description
of this function did not specifically mention either Windows or DOS
applications, so I tried it:
procedure RunIPOPT;
begin
WinExec(hWindow,'c:\xp\ipopt', sw_Show)
end;
- 54 -
This worked OK. However, the IPOPT program displays masses of
mathematical detail on screen as it is running. I do not want to confuse
the end users with this detail, so I want to eventually hide the window in
which it runs and bring up a message box to inform the user when the
program has run to completion. For the time being I will leave the window
visible (sw_Show) so that I can monitor progress:
procedure RunIPOPT;
begin
WinExec(hWindow,'c:\xp\ipopt',sw_Show);
MessageBox(hWindow, 'Program Completed','IPOPT', mb_Ok)
end;
Surely this should work. But, no! The message box came up on screen
before IPOPT had even begun to run.
Again all my books failed me, as did the Help file too this time.
Borland's Windows API guide had a description of the command, but there
were still no examples that showed how it might be used in practice. I was
stuck!
All fairy tales have happy endings, but they have to begin with "Once
upon a time ...".
Once upon a time I was browsing in my favorite computer book store and
discovered a copy of Neil Rubenking's Turbo Pascal for Windows Techniques
(1992). A cursory look at the index found a reference to the WinExec
function. I looked it up. It had exactly what I wanted. I bought a copy
immediately. The book is another excellent reference that I now have on my
shelf and reach for often.
It seems that the crucial point I was missing was the need to create a
loop to test whether the DOS program was still running. I adapted Neil
Rubenking's code to produce the following procedure:
procedure RunDos(hWindow: hWnd; CommandLine, CompletionMessage,
CompletionTitle: PChar);
var IH : word;
M : TMsg;
begin
IH := WinExec(CommandLine, SW_Show);
if IH <= 32 then
MessageBox(hWindow, CommandLine,
'Failed to run'+chr(13)+chr(10)+'Execution Error'+
chr(13)+chr(10)+'Contact Jim Youngman for Help',
mb_Ok + mb_IconHand)
else
begin
repeat
while PeekMessage(M, 0, 0, 0, PM_REMOVE) do
- 55 -
begin
if M.Message = WM_QUIT then
begin
PostQuitMessage(M.wParam);
Exit;
end
else
begin
TranslateMessage(M);
DispatchMessage(M);
end;
end; {end do}
until GetModuleUsage(IH) = 0;
MessageBox(HWindow, CompletionMessage, CompletionTitle,
mb_Ok + mb_IconInformation);
end; {else}
end;
I lived happily ever after, at least until the next problem.
- 56 -
Getting in touch with us:
Internet and Bitnet:
HJ647C at GWUVM.GWU.EDU -or- HJ647C at GWUVM.BITNET (Pete)
GEnie: P.DAVIS5 (Pete)
CompuServe: 71141,2071 (Mike)
WPJ BBS (703) 503-3021 (Mike and Pete)
You can also send paper mail to:
Windows Programmer's Journal
9436 Mirror Pond Drive
Fairfax, VA 22032
U.S.A.
In future issues we will be posting e-mail addresses of contributors
and columnists who don't mind you knowing their addresses. We will also
contact any writers from the first two issues and see if they want their
mail addresses made available for you to respond to them. For now, send
your comments to us and we'll forward them.
- 57 -
The Last Page
by Mike Wallace
Things have been hectic in the WPJ offices (i.e., our basement) -
you've been sending us great articles and mail with lots of input (see the
Letters column). It's been great - you seem to like what we're doing, and
we're having a good time. This month a tips/tricks column (Hacker's Gash)
makes its debut in WPJ (Official slogan: "The original party snack"). It
was written by us, but we're always glad to hear from you people. If you
have any tricks you want to share, let us know about them! We seem to be
accomplishing our basic goal of helping people learn how to program in
Windows, but this goal brings up an issue I want to write a bit about.
In the Letters column, there is a letter from Tammy Steele, formerly
of the SDK forum on CompuServe, and in her letter she discusses the
accuracy of WPJ. If the magazine isn't accurate, the Microsoft sysops
aren't going to recommend WPJ as a source of helpful info. When we started
this effort, I didn't envision Microsoft referring people needing help our
way. Don't ask me why, I guess it just never occurred to me, but it
illustrated to me the point that the available sources of help on this
matter shouldn't be exclusive - that is, no one has a monolopy on this
field (nor should anyone), because no one source can be everything to
everybody. I'm not going to tell you that WPJ can answer every question
you ever had about programming in Windows, because different sources offer
different things to their readers. We write about the topics we think the
most people can benefit from, but in some ways we're much different than,
say, "Windows/DOS Developer's Journal". Not to say we're better, just
different. Don't limit yourself to just one source of information.
If you have a specific question about Windows programming, you have
several options: write us a letter and we'll try to answer it, or, if you
have a ID on CompuServe, America Online, GEnie, etc., post it and see if
you get a response. Microsoft also puts out a Developer CD full of
articles (yes, I have this CD and it's a great source). What I'm leading
up to is that the sysops for these services (and their members) help people
learn this stuff, but sometimes a complete answer isn't possible (due to
space constraints), so why not refer someone to WPJ if we have an article
that answers the question? Much like I frequently refer to The Waite
Group's Windows API Bible (see review in this issue), I also read some of
the other Windows programming magazines. Sometimes they help, sometimes
not. As long as WPJ is useful and offers something you can't get
elsewhere, we'll stay around. A magazine shouldn't exist for its own sake,
and judging from your response, we're succeeding in filling a niche. Pete
and I didn't start this because we were bored; we saw an area we thought
wasn't getting addressed, namely, help beginners learn the basics, while
offering advanced articles for the people who have been around for a while.
If you don't like what we're doing, send us a note. If we keep getting
positive letters and don't hear anything to the contrary, we'll continue
what we're doing. Tammy's concerns about accuracy are well-founded. We'll
be the first ones to admit it if we make a mistake. I learn just as much
from our contributing authors as you do, but if a mistake slips through, I
want to know about it. With your help, we can make WPJ a helpful source of
- 58 -
info. 'Nuff said.
- 59 -